﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;

/*=====================================================================
  This file is part of a Microsoft SQL Server Shared Source Application.
  Copyright (C) Microsoft Corporation.  All rights reserved.
 
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
======================================================= */


namespace Microsoft.SqlCommunity.FaultRetry
{
    /// <summary>
    /// This class is designed to isolate the details of how data access will work from the rest of the application.  
    /// This class facilitates the instantiation and use of the myrad classes defined in ProductDataSet.cs.  At the 
    /// moment the approach is to used strongly typed data sets, but it should be possible to swap out to a different
    /// implementation if support for custom .NET data providers is improved for technologies like LINQ to SQL.
    /// </summary>
    public class DataContext
    {

        protected ProductDataSet _dataSet;  //Where the data is stored
        protected TableAdapterManager _manager;  //The transfer mechanism between the data set and the database
        //This event is raised if the underlying SqlConnection is closed.  The connection can be closed if the principal 
        //goes down, for example.
        private EventHandler<EventArgs> _connectionResetHandler; 

        /// <summary>
        /// Returns a data table which represents the set of all product categories.
        /// </summary>
        public ProductCategoryDataTable ProductCategories
        {
            get { return _dataSet.ProductCategory; }
        }

        /// <summary>
        /// Returns a data table which represents a set of products
        /// </summary>
        public ProductDataTable Products
        {
            get { return _dataSet.Product; }
        }

        /// <summary>
        /// Initialize the data context from the specified source of data (a MirrorConnection).  Data about products and product
        /// categories is loaded into the appropriate data tables.
        /// </summary>
        /// <param name="connection"></param>
        public DataContext(FaultRetryConnection connection)
        {
            _dataSet = new ProductDataSet();
            _manager = new TableAdapterManager();
            _connectionResetHandler = new EventHandler<EventArgs>(InternalConnectionResetHandler);
            // If the SqlConnection underlying the MirrorConnection is reset take appropriate action. See below.
            connection.InternalConnectionReset += _connectionResetHandler;
            _manager.Connection = connection;
            _manager.ProductCategoryTableAdapter = new ProductCategoryTableAdapter();
            _manager.ProductTableAdapter = new ProductTableAdapter();
            _manager.ProductCategoryTableAdapter.Connection = connection;
            _manager.ProductTableAdapter.Connection = connection;
            _dataSet.ProductCategory.Merge(_manager.ProductCategoryTableAdapter.GetData());
            _dataSet.Product.Merge(_manager.ProductTableAdapter.GetData());
            _dataSet.AcceptChanges();
            
        }

        /// <summary>
        /// Use the TableAdapterManager to insert/updated/delete rows in the database based on changes in the data set.
        /// </summary>
        public void SubmitChanges()
        {
            _manager.UpdateAll(_dataSet);
        }

        /// <summary>
        /// Determine if any data has changed by examining each row in each table to see if something has changed.
        /// </summary>
        /// <returns>True if at least one row has changed, false otherwise.</returns>
        public bool IsDataChanged()
        {
            DataSet changes = _dataSet.GetChanges();
            if (changes != null)
            {
                foreach (DataTable table in changes.Tables)
                {
                    foreach (DataRow dr in table.Rows)
                    {
                        if (dr.RowState == DataRowState.Added || dr.RowState == DataRowState.Deleted || dr.RowState == DataRowState.Modified)
                            return true;
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Invoked when the SqlConnection underlying the MirrorConnection is reset.  Invokes the setter 
        /// for the connection on the TableAdapterManager.  This will percolate the connection change down to any
        /// commands associated with the TableAdapterManager.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void InternalConnectionResetHandler(object sender, EventArgs e)
        {
            _manager.Connection = (FaultRetryConnection)sender;
        }

        /// <summary>
        /// When shutting down the data context remove the connection reset handler as
        /// so the TableAdapterManager is not updated when the connection is being manually shut
        /// down (as opposed to closing due to the principal going down).
        /// </summary>
        public void Close()
        {
            FaultRetryConnection managerConnection = _manager.Connection as FaultRetryConnection;
            if (managerConnection != null)
            {
                managerConnection.InternalConnectionReset -= _connectionResetHandler;
            }
        }
    }
}
